#include "vas_cache.h"

namespace vas {

    void CacheInit() {
        if (CACHE == nullptr) {
            CACHE = new std::map<std::string, std::vector<GCacheData>>();
        }
    }

    void ValidNameCheck(const std::string &name) {
        if (std::string::npos != name.find_first_of(SPLIT)) {
            throw VasInvalidValue("name " + name + " cannot contains " + SPLIT);
        }
    }

    std::string UUIDEncode(const std::string &key, const std::string &group, const std::string &name_space) {
        ValidNameCheck(name_space);
        ValidNameCheck(group);
        ValidNameCheck(key);
        return name_space + SPLIT + group + SPLIT + key;
    }

    bool DataExistCheck(const std::string &uuid) {
        if (CACHE == nullptr) {
            throw VasRuntimeError("Cache is not initialized, call CacheInit first.");
        }
        return CACHE->find(uuid) != CACHE->end();
    }

    void SaveValue(const std::string &key, void *value, int keep_size, const std::string &group,
                   const std::string &name_space) {
        if (CACHE == nullptr) {
            throw VasRuntimeError("Cache is not initialized, call CacheInit first.");
        }
        if (keep_size < MIN_KEEP_SIZE || keep_size > MAX_KEEP_SIZE) {
            throw VasInvalidValue("cache keep size" + std::to_string(keep_size) + " should between " +
                                  std::to_string(MIN_KEEP_SIZE) + " and " + std::to_string(MAX_KEEP_SIZE));
        }
        std::string uuid = UUIDEncode(key, group, name_space);
        auto *data = new GCacheData();
        data->name_space = name_space;
        data->group = group;
        data->key = key;
        data->keep_size = keep_size;
        data->ctime = GetCurrentTime();
        data->mtime = data->ctime;
        data->ptr = value;
        std::lock_guard<std::mutex> lock(CACHE_MTX);
        if (!DataExistCheck(uuid)) {
            // insert new value
            CACHE->insert(std::pair<std::string, std::vector<GCacheData>>(uuid, {*data}));
            return;
        }
        // update old value
        data->ctime = CACHE->at(uuid).front().ctime;
        while (keep_size <= CACHE->at(uuid).size()) {
            CACHE->at(uuid).pop_back();
        }
        CACHE->at(uuid).emplace(CACHE->at(uuid).begin(), *data);
    }

    const GCacheData &GetData(const std::string &key, const std::string &group, const std::string &name_space) {
        if (CACHE == nullptr) {
            throw VasRuntimeError("Cache is not initialized, call CacheInit first.");
        }
        std::string uuid = UUIDEncode(key, group, name_space);
        if (DataExistCheck(uuid)) {
            return CACHE->at(uuid).front();
        }
        throw VasNotFound("data at " + uuid + " is not found");
    }

    void *GetValue(const std::string &key, const std::string &group, const std::string &name_space) {
        return GetData(key, group, name_space).ptr;
    }

    const std::vector<GCacheData> &GetDataList(
            const std::string &key,
            const std::string &group,
            const std::string &name_space) {
        if (CACHE == nullptr) {
            throw VasRuntimeError("Cache is not initialized, call CacheInit first.");
        }
        std::string uuid = UUIDEncode(key, group, name_space);
        if (DataExistCheck(uuid)) {
            return CACHE->at(uuid);
        }
        throw VasNotFound("data at " + uuid + " is not found");
    }

    std::vector<const void *> GetValues(
            const std::string &key,
            const std::string &group,
            const std::string &name_space) {

        std::vector<GCacheData> data_list = GetDataList(key, group, name_space);
        std::vector<const void *> values;
        for (const auto &data:data_list) {
            values.emplace_back(data.ptr);
        }
        return values;
    }
}

